/*
 * File      : pthread_mutex.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2010, RT-Thread Development Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-10-26     Bernard      the first version
 */

#include <rtthread.h>
#include "pthread.h"

#define  MUTEXATTR_SHARED_MASK 0x0010
#define  MUTEXATTR_TYPE_MASK   0x000f

const pthread_mutexattr_t pthread_default_mutexattr = PTHREAD_PROCESS_PRIVATE;

int pthread_mutexattr_init(pthread_mutexattr_t* attr)
{
	if(attr) {
		*attr = pthread_default_mutexattr;

		return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_init);

int pthread_mutexattr_destroy(pthread_mutexattr_t* attr)
{
	if(attr) {
		*attr = -1;

		return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_destroy);

int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type)
{
	if(attr && type) {
		int  atype = (*attr & MUTEXATTR_TYPE_MASK);

		if(atype >= PTHREAD_MUTEX_NORMAL && atype <= PTHREAD_MUTEX_ERRORCHECK) {
			*type = atype;

			return 0;
		}
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_gettype);

int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type)
{
	if(attr && type >= PTHREAD_MUTEX_NORMAL && type <= PTHREAD_MUTEX_ERRORCHECK) {
		*attr = (*attr & ~MUTEXATTR_TYPE_MASK) | type;

		return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_settype);

int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared)
{
	if(!attr)
		return EINVAL;

	switch(pshared) {
		case PTHREAD_PROCESS_PRIVATE:
			*attr &= ~MUTEXATTR_SHARED_MASK;
			return 0;

		case PTHREAD_PROCESS_SHARED:
			*attr |= MUTEXATTR_SHARED_MASK;
			return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_setpshared);

int pthread_mutexattr_getpshared(pthread_mutexattr_t* attr, int* pshared)
{
	if(!attr || !pshared)
		return EINVAL;

	*pshared = (*attr & MUTEXATTR_SHARED_MASK) ? PTHREAD_PROCESS_SHARED
	           : PTHREAD_PROCESS_PRIVATE;
	return 0;
}
RTM_EXPORT(pthread_mutexattr_getpshared);

int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr)
{
	rt_err_t result;
	char name[RT_NAME_MAX];
	static rt_uint16_t pthread_mutex_number = 0;

	if(!mutex)
		return EINVAL;

	/* build mutex name */
	rt_snprintf(name, sizeof(name), "pmtx%02d", pthread_mutex_number ++);

	if(attr == RT_NULL)
		mutex->attr = pthread_default_mutexattr;
	else
		mutex->attr = *attr;

	/* init mutex lock */
	result = rt_mutex_init(&(mutex->lock), name, RT_IPC_FLAG_FIFO);

	if(result != RT_EOK)
		return EINVAL;

	/* detach the object from system object container */
	rt_object_detach(&(mutex->lock.parent.parent));

	return 0;
}
RTM_EXPORT(pthread_mutex_init);

int pthread_mutex_destroy(pthread_mutex_t* mutex)
{
	if(!mutex || mutex->attr == -1)
		return EINVAL;

	/* it's busy */
	if(mutex->lock.owner != RT_NULL)
		return EBUSY;

	rt_memset(mutex, 0, sizeof(pthread_mutex_t));
	mutex->attr = -1;

	return 0;
}
RTM_EXPORT(pthread_mutex_destroy);

int pthread_mutex_lock(pthread_mutex_t* mutex)
{
	int mtype;
	rt_err_t result;

	if(!mutex)
		return EINVAL;

	if(mutex->attr == -1) {
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	mtype = mutex->attr & MUTEXATTR_TYPE_MASK;
	rt_enter_critical();

	if(mutex->lock.owner == rt_thread_self() &&
	        mtype != PTHREAD_MUTEX_RECURSIVE) {
		rt_exit_critical();

		return EDEADLK;
	}

	rt_exit_critical();

	result = rt_mutex_take(&(mutex->lock), RT_WAITING_FOREVER);

	if(result == RT_EOK)
		return 0;

	return EINVAL;
}
RTM_EXPORT(pthread_mutex_lock);

int pthread_mutex_unlock(pthread_mutex_t* mutex)
{
	rt_err_t result;

	if(!mutex)
		return EINVAL;

	if(mutex->attr == -1) {
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	if(mutex->lock.owner != rt_thread_self()) {
		int mtype;
		mtype = mutex->attr & MUTEXATTR_TYPE_MASK;

		/* error check, return EPERM */
		if(mtype == PTHREAD_MUTEX_ERRORCHECK)
			return EPERM;

		/* no thread waiting on this mutex */
		if(mutex->lock.owner == RT_NULL)
			return 0;
	}

	result = rt_mutex_release(&(mutex->lock));

	if(result == RT_EOK)
		return 0;

	return EINVAL;
}
RTM_EXPORT(pthread_mutex_unlock);

int pthread_mutex_trylock(pthread_mutex_t* mutex)
{
	rt_err_t result;
	int mtype;

	if(!mutex)
		return EINVAL;

	if(mutex->attr == -1) {
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	mtype = mutex->attr & MUTEXATTR_TYPE_MASK;
	rt_enter_critical();

	if(mutex->lock.owner == rt_thread_self() &&
	        mtype != PTHREAD_MUTEX_RECURSIVE) {
		rt_exit_critical();

		return EDEADLK;
	}

	rt_exit_critical();

	result = rt_mutex_take(&(mutex->lock), 0);

	if(result == RT_EOK) return 0;

	return EBUSY;
}
RTM_EXPORT(pthread_mutex_trylock);
